package org.jili.novoland.base;

import java.util.Vector;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

/**
 * A TiledLayer is a visual element composed of a grid of cells that can be
 * filled with a set of tile images. This class allows large virtual layers to
 * be created without the need for an extremely large Image. This technique is
 * commonly used in 2D gaming platforms to create very large scrolling
 * backgrounds.
 * <hr>
 * 
 */
public class TiledLayer extends Layer {

    int windowX = 0;
    int windowY = 0;
    int windowWidth = 0;
    int windowHeight = 0;

    /**
     * Creates a new TiledLayer. The TiledLayer's grid will be rows cells high
     * and columns cells wide. All cells in the grid are initially empty (i.e.
     * they contain tile index 0). The contents of the grid may be modified
     * through the use of setCell(int, int, int) and fillCells(int, int, int,
     * int, int). The static tile set for the TiledLayer is created from the
     * specified Image with each tile having the dimensions of tileWidth x
     * tileHeight. The width of the source image must be an integer multiple of
     * the tile width, and the height of the source image must be an integer
     * multiple of the tile height; otherwise, an IllegalArgumentException is
     * thrown;The entire static tile set can be changed using
     * setStaticTileSet(Image, int, int). These methods should be used sparingly
     * since they are both memory and time consuming. Where possible, animated
     * tiles should be used instead to animate tile appearance.
     * 
     * @param cols
     *            the width of the TiledLayer, expressed as a number of cells
     * @param rows
     *            the height of the TiledLayer, expressed as a number of cells
     * @param img
     *            the Image to use for creating the static tile set
     * @param tileWidth
     *            the width in pixels of a single tile
     * @param tileHeight
     *            the height in pixels of a single tile
     */
    public TiledLayer(int cols, int rows, Bitmap img, int tileWidth,
            int tileHeight) {
        // the specification doesn't states if the TiledLayer is visible on
        // creation
        // we assume it is
        super(cols * tileWidth, rows * tileHeight);
        windowWidth = width;
        windowHeight = height;

        if (img == null)
            throw new NullPointerException();
        if (cols <= 0 || rows <= 0 || tileHeight <= 0 || tileWidth <= 0)
            throw new IllegalArgumentException();
        if (img.getWidth() % tileWidth != 0
                || img.getHeight() % tileHeight != 0) {
            Log.v("",
                    Integer.toString(img.getWidth()) + ","
                            + Integer.toString(tileWidth) + ":"
                            + img.getWidth() % tileWidth);
            Log.v("",
                    Integer.toString(img.getHeight()) + ","
                            + Integer.toString(tileHeight) + ":"
                            + img.getHeight() % tileHeight);
            throw new IllegalArgumentException();
        }

        this.img = img;
        this.cols = cols;
        this.rows = rows;
        this.tileWidth = tileWidth;
        this.tileHeight = tileHeight;
        this.numStaticTiles = (img.getWidth() / tileWidth)
                * (img.getHeight() / tileHeight);
        this.cellMatrix = new int[rows][cols];
        this.animatedTiles = new Vector<Integer>();
    }

    public void setWindowPostion(int windowX, int windowY) {
        if (windowX < 0)
            windowX = 0;
        if (windowY < 0)
            windowY = 0;
        if (windowX + windowWidth > cols * tileWidth)
            windowX = cols * tileWidth - windowWidth;
        if (windowY + windowHeight > rows * tileHeight)
            windowY = rows * tileHeight - windowHeight;

        this.windowX = windowX;
        this.windowY = windowY;
    }

    public void moveWindowPostion(int dX, int dY) {
        if (windowX - dX < 0)
            dX = 0;
        if (windowY - dY < 0)
            dY = 0;
        if (windowX - dX + windowWidth > width)
            dX = windowX + windowWidth - width;
        if (windowY - dY + windowHeight > height)
            dY = windowY + windowHeight - height;
        if (windowY > 725){
            Log.v(",","");
            return;
        }
        this.windowX -= dX;
        this.windowY -= dY;
    }

    public void setWindowSize(int windowWidth, int windowHeight) {
        if (windowWidth < 0)
            windowWidth = 0;
        if (windowHeight < 0)
            windowHeight = 0;
        if (windowWidth > cols * tileWidth)
            windowWidth = cols * tileWidth;
        if (windowHeight > rows * tileHeight)
            windowHeight = rows * tileHeight;
        this.windowWidth = windowWidth;
        this.windowHeight = windowHeight;
    }

    /**
     * Creates a new animated tile and returns the index that refers to the new
     * animated tile. It is initially associated with the specified tile index
     * (either a static tile or 0). The indices for animated tiles are always
     * negative. The first animated tile shall have the index -1, the second,
     * -2, etc.
     */
    public int createAnimatedTile(int staticTileIndex) {
        synchronized (this) {
            if (staticTileIndex < 0 || staticTileIndex > numStaticTiles)
                throw new IndexOutOfBoundsException();

            animatedTiles.add(staticTileIndex);
            return -animatedTiles.size();
        }
    }

    /**
     * Gets the tile referenced by an animated tile.
     * 
     * @param index
     *            the index of the animated tile
     * @return Returns the tile index currently associated with the animated
     *         tile.
     */
    public int getAnimatedTile(int index) {
        synchronized (this) {
            index = -index - 1;
            if (index < 0 || index >= animatedTiles.size())
                throw new IndexOutOfBoundsException();
            return animatedTiles.get(index);
        }
    }

    /**
     * Associates an animated tile with the specified static tile.
     * 
     * @param index
     *            the index of the animated tile
     * @param staticTileIndex
     *            the index of the associated tile (must be 0 or a valid static
     *            tile index) .
     */
    public void setAnimatedTile(int index, int staticTileIndex) {
        synchronized (this) {
            index = -index - 1;
            if (index < 0 || index >= animatedTiles.size())
                throw new IndexOutOfBoundsException();
            if (staticTileIndex < 0 || staticTileIndex > numStaticTiles)
                throw new IndexOutOfBoundsException();

            animatedTiles.set(index, staticTileIndex);
        }
    }

    /**
     * Gets the contents of a cell. Gets the index of the static or animated
     * tile currently displayed in a cell. The returned index will be 0 if the
     * cell is empty.
     * 
     * @param col
     *            the column of cell to check
     * @param row
     *            the row of cell to check
     * @return the index of tile in cell
     */
    public int getCell(int col, int row) {
        return this.cellMatrix[row][col];
    }

    /**
     * Sets the contents of a cell. The contents may be set to a static tile
     * index, an animated tile index, or it may be left empty (index 0)
     * 
     * @param col
     *            the column of cell to set
     * @param row
     *            the row of cell to set
     * @param index
     *            the index of tile to place in cell
     */
    public void setCell(int col, int row, int index) {
        synchronized (this) {
            if (-index - 1 >= animatedTiles.size() || index > numStaticTiles)
                throw new IndexOutOfBoundsException();
            cellMatrix[row][col] = index;
        }
    }

    /**
     * Change the static tile set. Replaces the current static tile set with a
     * new static tile set. See the constructor TiledLayer(int, int, Image, int,
     * int) for information on how the tiles are created from the image.If the
     * new static tile set has as many or more tiles than the previous static
     * tile set, the the animated tiles and cell contents will be preserve. If
     * not, the contents of the grid will be cleared (all cells will contain
     * index 0) and all animated tiles will be deleted.
     * 
     * @param image
     *            the Image to use for creating the static tile set
     * @param tileWidth
     *            the width in pixels of a single tile
     * @param tileHeight
     *            the height in pixels of a single tile
     */
    public void setStaticTileSet(Bitmap image, int tileWidth, int tileHeight) {
        synchronized (this) {
            if (img == null)
                throw new NullPointerException();
            if (tileHeight <= 0 || tileWidth <= 0)
                throw new IllegalArgumentException();
            if (img.getWidth() % tileWidth != 0
                    || img.getHeight() % tileHeight != 0)
                throw new IllegalArgumentException();

            int newNumStaticTiles = (img.getWidth() / getCellWidth())
                    * (img.getHeight() / getCellHeight());

            // recalculate size
            int w = cols * tileWidth;
            int h = rows * tileHeight;

            setSize(w, h);
            this.tileWidth = tileWidth;
            this.tileHeight = tileHeight;

            if (newNumStaticTiles >= numStaticTiles) {
                this.numStaticTiles = newNumStaticTiles;
                return;
            }
            // if there are less static tiles
            // all animated tiles are discarded and
            // the tiledLayer is filled with tiles with index 0

            this.numStaticTiles = newNumStaticTiles;
            this.animatedTiles = new Vector<Integer>();
            this.fillCells(0, 0, getColumns(), getRows(), 0);
        }
    }

    /**
     * Fills a region cells with the specific tile. The cells may be filled with
     * a static tile index, an animated tile index, or they may be left empty
     * (index 0).
     * 
     * @param col
     *            the column of top-left cell in the region
     * @param row
     *            the row of top-left cell in the region
     * @param numCols
     *            the number of columns in the region
     * @param numRows
     *            the number of rows in the region
     * @param index
     *            the Index of the tile to place in all cells in the specified
     *            region
     */
    public void fillCells(int col, int row, int numCols, int numRows, int index) {
        synchronized (this) {
            if (numCols < 0 || numRows < 0)
                throw new IllegalArgumentException();
            if (row < 0 || col < 0 || col + numCols > this.cols
                    || row + numRows > this.rows)
                throw new IndexOutOfBoundsException();
            if (-index - 1 >= animatedTiles.size() || index > numStaticTiles)
                throw new IndexOutOfBoundsException();

            int rMax = row + numRows;
            int cMax = col + numCols;
            for (int r = row; r < rMax; r++) {
                for (int c = col; c < cMax; c++) {
                    cellMatrix[r][c] = index;
                }
            }
        }
    }

    /**
     * Gets the number of columns in the TiledLayer grid.
     * 
     * @return the width in columns of the TiledLayer grid
     */
    public final int getColumns() {
        return cols;
    }

    /**
     * Gets the number of rows in the TiledLayer grid.
     * 
     * @return the width in rows of the TiledLayer grid
     */
    public final int getRows() {
        return rows;
    }

    /**
     * Gets the width of a single cell, in pixels.
     * 
     * @return the width in pixels of a single cell in the TiledLayer grid
     */
    public final int getCellWidth() {
        return tileWidth;
    }

    /**
     * Gets the height of a single cell, in pixels.
     * 
     * @return the height in pixels of a single cell in the TiledLayer grid
     */
    public final int getCellHeight() {
        return tileHeight;
    }

    /**
     * Draws the TiledLayer. The entire TiledLayer is rendered subject to the
     * clip region of the Graphics object. The TiledLayer's upper left corner is
     * rendered at the TiledLayer's current position relative to the origin of
     * the Graphics object. The current position of the TiledLayer's upper-left
     * corner can be retrieved by calling Layer.getX() and Layer.getY(). The
     * appropriate use of a clip region and/or translation allows an arbitrary
     * region of the TiledLayer to be rendered.
     * 
     * @param g
     *            the graphics object to draw the TiledLayer
     */
    public final void paint(Canvas g) {
        synchronized (this) {
            if (!this.isVisible())
                return;

            int imgCols = img.getWidth() / tileWidth;
            int windowCol = windowX / tileWidth;
            int windowRow = windowY / tileHeight;

            for (int col0 = windowCol; col0 < Math.min(
                    (int) (windowCol + windowWidth / tileWidth), cols); col0++) {
                int dstX = x + (col0 - windowCol) * tileWidth;
                for (int row0 = windowRow; row0 < Math.min(
                        (int) (windowRow + windowHeight / tileHeight), rows); row0++) {
                    int dstY = y + (row0 - windowRow) * tileHeight;
                    int tile = getCell(col0, row0);
                    if (tile < 0)
                        tile = getAnimatedTile(tile);
                    if (tile == 0)
                        continue;

                    tile--;

                    int xSrc = tileWidth * (tile % imgCols);
                    int ySrc = (tile / imgCols) * tileHeight;
                    Rect srcRect = new Rect(xSrc, ySrc, xSrc + tileWidth, ySrc
                            + tileHeight);
                    Rect dstRect = new Rect(dstX, dstY, dstX +
                             tileWidth, dstY + tileHeight);
                    Log.v("----",
                            Integer.toString(col0) + ","
                                    + Integer.toString(row0));
                    g.drawBitmap(img, srcRect, dstRect, null);
                }
            }
        }
    }

    private final int rows, cols;
    // package access for collision detection
    private Bitmap img;
    public Bitmap getImg(){
        return img;
    }
    

    private int tileHeight, tileWidth, numStaticTiles;

    // the list of anmated tiles
    // NOTE the first animatedTile (index -1) goes
    // into the first position in the array (index 0)
    // so to access the correct tile use animatedTiles[-n-1]
    Vector<Integer> animatedTiles;
    // the matrix for storing the tiles
    private int[][] cellMatrix;
}
